#include "viewer.hpp"
#include <iostream>
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>

extern GLuint my_index;
extern NodeNameID m_node_IDs[50];
Viewer::Viewer(SceneNode *n)
{
  Glib::RefPtr<Gdk::GL::Config> glconfig;

  // Ask for an OpenGL Setup with
  //  - red, green and blue component colour
  //  - a depth buffer to avoid things overlapping wrongly
  //  - double-buffered rendering to avoid tearing/flickering
  glconfig = Gdk::GL::Config::create(Gdk::GL::MODE_RGB |
                                     Gdk::GL::MODE_DEPTH |
                                     Gdk::GL::MODE_DOUBLE);
  if (glconfig == 0) {
    // If we can't get this configuration, die
    std::cerr << "Unable to setup OpenGL Configuration!" << std::endl;
    abort();
  }

  // Accept the configuration
  set_gl_capability(glconfig);

  // Register the fact that we want to receive these events
  add_events(Gdk::BUTTON1_MOTION_MASK    |
             Gdk::BUTTON2_MOTION_MASK    |
             Gdk::BUTTON3_MOTION_MASK    |
             Gdk::BUTTON_PRESS_MASK      | 
             Gdk::BUTTON_RELEASE_MASK    |
			 Gdk::POINTER_MOTION_MASK	 |
             Gdk::VISIBILITY_NOTIFY_MASK);
	// initialize root node;	
	puppet = n;
}

Viewer::~Viewer()
{
  // Nothing to do here right now.
}

void Viewer::initialize()
{
	// initialize transform matrix
    button1 = false;
    button2 = false;
    button3 = false;
	// initialize mode
	m_mode = PositionOrientation;
	// Transform matrices
	m_translation = Matrix4x4();
	m_rotation = Matrix4x4();
	// lighting
	GLfloat white_light[] = {1.0, 1.0, 1.0, 1.0};
	GLfloat light_position[] = {0.0, 0.0, 10.0, 0.0};
	glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHTING);
	// enable culling
	glEnable(GL_NORMALIZE);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	//glEnable(GL_COLOR_MATERIAL);
	
	m_circle = false;
	m_zbuffer = false;
	m_backcull = false;
	m_frontcull = false;
}

void Viewer::invalidate()
{
  // Force a rerender
  Gtk::Allocation allocation = get_allocation();
  get_window()->invalidate_rect( allocation, false);
}

void Viewer::on_realize()
{
  // Do some OpenGL setup.
  // First, let the base class do whatever it needs to
  Gtk::GL::DrawingArea::on_realize();
  
  Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();
  
  if (!gldrawable)
    return;

  if (!gldrawable->gl_begin(get_gl_context()))
    return;

  glShadeModel(GL_SMOOTH);
  glClearColor( 0.4, 0.4, 0.4, 0.0 );
  glEnable(GL_DEPTH_TEST);
  gldrawable->gl_end();
initialize();
my_index = glGenLists(1);
glNewList(my_index, GL_COMPILE);
GLUquadric* quadric = gluNewQuadric();
gluQuadricNormals(quadric, GLU_SMOOTH);
gluSphere(quadric, 1.0f, 100, 100);
glEndList();
}

bool Viewer::on_expose_event(GdkEventExpose* event)
{
  Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();

  if (!gldrawable) return false;

  if (!gldrawable->gl_begin(get_gl_context()))
    return false;
	
	// Set up for perspective drawing 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glViewport(0, 0, get_width(), get_height());
  gluPerspective(40.0, (GLfloat)get_width()/(GLfloat)get_height(), 0.1, 1000.0);

  // change to model view for drawing
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
	// Set up lighting
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
		
	// update edit_menu
	update_editmenu();
	
  // Clear framebuffer
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// z-buffer
	if( m_zbuffer )  glEnable(GL_DEPTH_TEST);
	else glDisable(GL_DEPTH_TEST);	
	
	if( m_backcull && m_frontcull ){
		glEnable(GL_CULL_FACE);
		glCullFace(GL_FRONT_AND_BACK);
	}
	else if( m_backcull && !m_frontcull ){
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
	}
	else if( !m_backcull && m_frontcull ){
		glEnable(GL_CULL_FACE);
		glCullFace(GL_FRONT);
	}
	else{ 		
		glDisable(GL_CULL_FACE);
	}

  // Draw stuff
	draw_puppet();
	if( m_circle ) draw_trackball_circle();
	
  // Swap the contents of the front and back buffers so we see what we
  // just drew. This should only be done if double buffering is enabled.
  gldrawable->swap_buffers();

  gldrawable->gl_end();

  return true;
}

bool Viewer::on_configure_event(GdkEventConfigure* event)
{
  Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();

  if (!gldrawable) return false;
  
  if (!gldrawable->gl_begin(get_gl_context()))
    return false;

  // Set up perspective projection, using current size and aspect
  // ratio of display

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glViewport(0, 0, event->width, event->height);
  gluPerspective(40.0, (GLfloat)event->width/(GLfloat)event->height, 0.1, 1000.0);

  // Reset to modelview matrix mode
  glMatrixMode(GL_MODELVIEW);
  gldrawable->gl_end();
  return true;
}

void Viewer::draw_trackball_circle()
{
  int current_width = get_width();
  int current_height = get_height();
  // Set up for orthogonal drawing to draw a circle on screen.
  // You'll want to make the rest of the function conditional on
  // whether or not we want to draw the circle this time around.
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glViewport(0, 0, current_width, current_height);
  glOrtho(0.0, (float)current_width, 
           0.0, (float)current_height, -0.1, 0.1);

  // change to model view for drawing
  glMatrixMode(GL_MODELVIEW);

  // Reset modelview matrix
  glLoadIdentity();

  // draw a circle for use with the trackball 
  glDisable(GL_LIGHTING);
  glEnable(GL_LINE_SMOOTH);
  glColor3f(1.0, 1.0, 1.0);
  double radius = current_width < current_height ? 
    (float)current_width * 0.25 : (float)current_height * 0.25;
  glTranslated((float)current_width * 0.5, (float)current_height * 0.5, 0);
  glBegin(GL_LINE_LOOP);
  for(size_t i=0; i<40; ++i) {
    double cosine = radius * cos(i*2*M_PI/40);
    double sine = radius * sin(i*2*M_PI/40);
    glVertex2f(cosine, sine);
  }
  glEnd();
  glColor3f(0.0, 0.0, 0.0);
  glDisable(GL_LINE_SMOOTH);
}

void Viewer::draw_puppet(bool picking)
{
	puppet->set_transform(puppet->get_transform()*(m_translation*m_rotation));
	puppet->walk_gl(picking);
	puppet->set_transform(puppet->get_transform()*(m_translation*m_rotation).invert());
}

void Viewer::reset_all()
{
	static_cast<Gtk::CheckMenuItem*>(&option_menu->items()[0])->set_active(false);
	static_cast<Gtk::CheckMenuItem*>(&option_menu->items()[1])->set_active(false);
	static_cast<Gtk::CheckMenuItem*>(&option_menu->items()[2])->set_active(false);
	static_cast<Gtk::CheckMenuItem*>(&option_menu->items()[3])->set_active(false);
	static_cast<Gtk::RadioMenuItem*>(&mode_menu->items()[0])->set_active(true);
    reset_position();
    reset_orientation();
    reset_joints();
}

void Viewer::reset_position()
{
	m_translation = Matrix4x4();
	invalidate();
}

void Viewer::reset_orientation()
{
	m_rotation = Matrix4x4();
	invalidate();
}

void Viewer::reset_joints()
{
	puppet->reset_joints();
	JointNode* temp_node;
	if(!current_stack.empty()){
		for( STACKINFO::iterator f = current_stack.begin(); f != current_stack.end(); f++ ){
			temp_node = f->second.address;
			temp_node->highlight_children();
		}
		current_stack.clear();
	}
	while(undo_stack.size()!=0){
		undo_stack.pop();
	}
	while(redo_stack.size()!=0){
		redo_stack.pop();
	}
	invalidate();
}

void Viewer::set_circle()
{
	m_circle = !m_circle;
	invalidate();
}

void Viewer::set_zbuffer()
{
	m_zbuffer = !m_zbuffer;
	invalidate();
}

void Viewer::set_backcull()
{
	m_backcull = !m_backcull;
	invalidate();
}

void Viewer::set_frontcull()
{
	m_frontcull = !m_frontcull;
	invalidate();
}

void Viewer::set_mode(Mode m){
    m_mode = m;
}

void Viewer::set_undo_redo_menu(Gtk::Menu* m)
{
	edit_menu = m;
}

void Viewer::set_option_menu(Gtk::Menu* m)
{
	option_menu = m;
}

void Viewer::set_mode_menu(Gtk::Menu* m)
{
	mode_menu = m;
}
void Viewer::redo()
{
	JointNode* temp_node;
	if(redo_stack.empty()) return;
	STACKINFO temp_stack = redo_stack.top();
	redo_stack.pop();
	undo_stack.push(temp_stack); 
	// unpick current stack
	if(!current_stack.empty()){
		for( STACKINFO::iterator f = current_stack.begin(); f != current_stack.end(); f++ ){
			temp_node = f->second.address;
			temp_node->highlight_children();
		}
	}
	// update current stack
	for( STACKINFO::iterator f = temp_stack.begin(); f != temp_stack.end(); f++ ){
		temp_node = f->second.address;
		temp_node->highlight_children();
		temp_node->rotate_x(f->second.angle_x - temp_node->get_joint_x());
		temp_node->rotate_y(f->second.angle_y - temp_node->get_joint_y());
		f->second.angle_x = temp_node->get_joint_x();
		f->second.angle_y = temp_node->get_joint_y();
	}
	current_stack = temp_stack;
	invalidate();
}

void Viewer::undo()
{
	JointNode* temp_node;
	if(undo_stack.empty()) return;
	STACKINFO temp_stack = undo_stack.top();
	undo_stack.pop();
	// unpick current stack
	for( STACKINFO::iterator f = temp_stack.begin(); f != temp_stack.end(); f++ ){
		temp_node = f->second.address;
		temp_node->highlight_children();
	}
	// push to redo
	redo_stack.push(temp_stack);
	current_stack.clear();
	invalidate();
	if(undo_stack.empty()) return;
	// undate current stack
	temp_stack = undo_stack.top();
	for( STACKINFO::iterator f = temp_stack.begin(); f != temp_stack.end(); f++ ){
		temp_node = f->second.address;
		temp_node->highlight_children();
		temp_node->rotate_x(f->second.angle_x - temp_node->get_joint_x());
		temp_node->rotate_y(f->second.angle_y - temp_node->get_joint_y());
		f->second.angle_x = temp_node->get_joint_x();
		f->second.angle_y = temp_node->get_joint_y();
	}
	current_stack = temp_stack;
	invalidate();
}

void Viewer::pick_joints(int x, int y)
{
    GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT,viewport);
	glSelectBuffer(50, picking_buffer);
	glRenderMode(GL_SELECT);
	glInitNames();
    glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluPickMatrix(x ,viewport[3]-y, 1, 1, viewport);
	gluPerspective(40.0, (GLfloat)get_width()/(GLfloat)get_height(), 0.1, 1000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	draw_puppet(true);
	glMatrixMode(GL_PROJECTION); 
    glPopMatrix(); 
    glMatrixMode(GL_MODELVIEW);
	GLint hits = glRenderMode(GL_RENDER);
    post_pick(hits,picking_buffer);
	invalidate();
}

void Viewer::update_currentstack()
{
	// update current_stack
	JointNode* tempnode;  
	for ( STACKINFO::iterator f = current_stack.begin(); f != current_stack.end(); f++ ){
		tempnode = f->second.address;
		f->second.angle_x = tempnode->get_joint_x();
		f->second.angle_y = tempnode->get_joint_y();
	}
	undo_stack.push(current_stack);
	while(redo_stack.size() != 0){
		redo_stack.pop();
	}
}

void Viewer::update_editmenu()
{
	if( undo_stack.empty() )
		static_cast<Gtk::Widget*>(&edit_menu->items()[0])->set_sensitive(false);
	else
		static_cast<Gtk::Widget*>(&edit_menu->items()[0])->set_sensitive(true);
		
	if( redo_stack.empty() )
		static_cast<Gtk::Widget*>(&edit_menu->items()[1])->set_sensitive(false);
	else
		static_cast<Gtk::Widget*>(&edit_menu->items()[1])->set_sensitive(true);
}

void Viewer::post_pick(GLint hits, GLuint buffer[])
{
    if (hits == 0 )
        return;
	unsigned int deepth, node_id, hit_name_number;
	GLuint *temp = (GLuint *) buffer;
	for(int i=0; i<hits; i++)
	{
		hit_name_number = *temp;
		temp++;
		if(i==0){
			deepth = *temp;
			temp += 2;
			node_id = *temp;
			temp += hit_name_number;
		}
		else if(*temp < deepth)
		{
			deepth = *temp;
			temp += 2;
			node_id = *temp;
			temp += hit_name_number;
		}
		else{
			temp += 2 + hit_name_number;
		}
	}
	selected_joint = ((GeometryNode *)(m_node_IDs[node_id].getNode()))->get_parent(); 
	if(!selected_joint->is_joint()) return;
	selected_joint->highlight_children();
	bool temp_pick = selected_joint->get_pick();
	int temp_id = selected_joint->get_id();
	double temp_x = ((JointNode*)selected_joint)->get_joint_x();
	double temp_y = ((JointNode*)selected_joint)->get_joint_y();
	if(temp_pick){
		current_stack.insert(std::pair<int, JointNodeInfo>
			(temp_id, JointNodeInfo( (JointNode*)selected_joint, temp_x, temp_y )));
	}
	else{
		current_stack.erase(temp_id);
	}
	update_currentstack();
}

Matrix4x4 Viewer::translation(const Vector3D& displacement)
{
	Vector4D row1( 1.0, 0.0, 0.0, displacement[0]);
	Vector4D row2( 0.0, 1.0, 0.0, displacement[1]);
	Vector4D row3( 0.0, 0.0, 1.0, displacement[2]);
	Vector4D row4( 0.0, 0.0, 0.0, 1.0);
	return Matrix4x4(row1, row2,row3, row4);
}

Matrix4x4 Viewer::rotation(float fOldX, float fNewX, float fOldY, float fNewY)
{
	float  fRotVecX, fRotVecY, fRotVecZ;
	float fDiameter;
	int iCenterX, iCenterY;
	float fNewModX, fNewModY, fOldModX, fOldModY;
	int nWinWidth = get_width();
	int nWinHeight = get_height();
	fDiameter = (nWinWidth < nWinHeight) ? nWinWidth * 0.5 : nWinHeight * 0.5;
	iCenterX = nWinWidth / 2;
	iCenterY = nWinHeight / 2;
	fOldModX = fOldX - iCenterX;
	fOldModY = fOldY - iCenterY;
	fNewModX = fNewX - iCenterX;
	fNewModY = fNewY - iCenterY;
	/* vCalcRotVec expects new and old positions in relation to the center of the
	 * trackball circle which is centered in the middle of the window and
	 * half the smaller of nWinWidth or nWinHeight.
	  */
	vCalcRotVec(fNewModX, fNewModY,fOldModX, fOldModY,fDiameter,&fRotVecX, &fRotVecY, &fRotVecZ);
	/* Negate Y component since Y axis increases downwards
	 * in screen space and upwards in OpenGL.
	 */
	Matrix4x4 mNewMat;
	vAxisRotMatrix(fRotVecX, -fRotVecY, fRotVecZ, &mNewMat);
	return mNewMat;
}

bool Viewer::on_button_press_event(GdkEventButton* event)
{
//  std::cerr << "Stub: Button " << event->button << " pressed" << std::endl;
    switch (event->button) {
        case 1:
            button1 = true;
            break;
        case 2:
            button2 = true;
            break;
        case 3:
            button3 = true;
            break;
        default:
            break;
    }
    start_x = event->x;
    start_y = event->y;
    if( m_mode == Joints && button1 ){
        pick_joints(start_x, start_y);
    }
  return true;
}

bool Viewer::on_button_release_event(GdkEventButton* event)
{
//  std::cerr << "Stub: Button " << event->button << " released" << std::endl;
    switch (event->button) {
        case 1:
            button1 = false;
            break;
        case 2:
            button2 = false;
            break;
        case 3:
            button3 = false;
            break;
        default:
            break;
    }
	if( m_mode == Joints && (event->button == 2 || event->button == 3) ){
		if(!current_stack.empty())
			update_currentstack();
	}
  return true;
}

bool Viewer::on_motion_notify_event(GdkEventMotion* event)
{
//  std::cerr << "Stub: Motion at " << event->x << ", " << event->y << std::endl;
	if( !button1 && !button2 && !button3 ) return true;
    int delta_x = event->x - start_x;
    int delta_y = start_y - event->y;
    if (button1) {
		if(m_mode == PositionOrientation)
			m_translation = translation(Vector3D(delta_x/30.0, delta_y/23.0, 0.0)) * m_translation;
    }
    if (button2) {
		if(m_mode == PositionOrientation)
        	m_translation = translation(Vector3D(0.0, 0.0, -delta_y/10.0)) * m_translation;
		else if(m_mode == Joints){
			JointNode* tempnode;  
			for ( STACKINFO::iterator f = current_stack.begin(); f != current_stack.end(); f++ ){
				tempnode = f->second.address;
				tempnode->rotate_x(delta_x);
			}
		}
    }
    if (button3) {
		if(m_mode == PositionOrientation)
			m_rotation = rotation(start_x, event->x, start_y, event->y) *  m_rotation;
		else if(m_mode == Joints){
			JointNode* tempnode;  
			for ( STACKINFO::iterator f = current_stack.begin(); f != current_stack.end(); f++ ){
				tempnode = f->second.address;
				tempnode->rotate_y(delta_y);
			}
		}
    }
    invalidate();
    start_x = event->x;
    start_y = event->y;
  return true;
}
